<?php

https://github.com/alibaba/Sentinel/wiki/%E4%BB%8B%E7%BB%8D

1->负载均衡
   -) '两端负载'
       [服务端 - Nginx]
                                    +----- Tomcat1
       Client ------> Nginx --------+----- Tomcat2
                                    +----- Tomcat3
       说明: 由于[nginx]是部署在服务器上, 所以此模式得名'服务端负载均衡'.


       [客户端 - Gateway]
       '服务内部'

                          内容中心-实例1
       用户中心 ------->   内容中心-实例2    -------[内部负载均衡算法]获取请求的'单个实例地址'------> 内容中心-实例1
                          内容中心-实例3
       说明: 此种模式是在'服务内部'自定义负载均衡算法, 得到'具体实例地址', 然后进行请求, 所以得名'客户端端负载均衡'

   -) '自定义负载均衡'
       核心: 我们自定义负载均衡的话, 其实就是根据我们自定义算法, 计算后获取[List<ServiceInstance>]里面的一个实例, 然后给到[restTemplate]调用就行了!
       所以, 我们很容易就可以写出几个简单的负载算法 (随机, hash)
       int instanceIndex = ThreadLocalRandom.current().nextInt(serviceInstanceSize);


2->Ribbon
   -) '遗留问题'
       虽然上面我们可以实现负载, 但是这里有几个问题
       . 自己写负载算法较为麻烦
       . 一次[负载计算 到 发送请求], 我们写了很多的'冗余代码'
         - 负载算法选择实例
         - 拼接请求格式 (ip:port)

   -) 'Ribbon'
       我们想到的问题, 早已经有前者想到了, 而且也提供了解决方法, 那么就来康康是what吧...
       ribbon: netflix公司出品 ...
       核心: ribbon之所以能实现'负载均衡'是因为, 她可以从'注册中心'获取所有微服务实例, 然后集合指定的'负载均衡算法', 计算得出所要请求的实例
       使用:
       - pom (alibaba的pom内置ribbon)
       - 注解
         @Bean
         @LoadBalanced
         public RestTemplate restTemplate() {
            return new RestTemplate();
         }

       请求:
       - 原始:
         . DiscoveryClient获取实例集合
         . 负载算法计算获得特定实例
         . 请求

       - ribbon:
         restTemplate.getForObject("http://user-center/users/{userId}", UserDTO.class, userId)
         . 直接请求

         核心: ribbon会获取[user-center]去注册中心上查询'所有'该服务名, 然后使用负载算法, 计算返回一个实例.

   -) 'Ribbon组成'
       +==========================+=============================+======================================+
       |      IClientConfig       |           读取配置           |       DefaultClientConfigImpl        |
       +--------------------------+-----------------------------+--------------------------------------+
       |      ILoadBalancer       |   Ribbon入口 (你想要的她都有)  |       ZoneAwareLoadBalancer          |
       +--------------------------+-----------------------------+--------------------------------------+
       |          IRule           |     负载均衡策略, 选择实例     |       ZoneAvoidanceRule              |
       +--------------------------+-----------------------------+--------------------------------------+
       |          IPing           |     筛选掉ping不通的实例       |               DummyPing              |
       +--------------------------+-----------------------------+--------------------------------------+
       |    ServerList<Server>    |     Ribbon操作的实例列表      | Ribbon: ConfigurationBasedServerList |
       |                          |                             | SCA: nacosServerList                 |
       +--------------------------+-----------------------------+--------------------------------------+
       | ServerListFilter<Server> |     过滤掉不符合条件的实例      |    ZonePreferenceServerListfilter    |
       +--------------------------+-----------------------------+--------------------------------------+
       |    ServerListUpdater     |        动态更新服务实例        |       PollingServerListUpdater       |
       +==========================+====================================================================+

   -) '饥饿加载'
       注意: ribbon请求模式是'懒加载', 所以会在'初次请求'时过慢, 影响相应时间, 这个我们可以配置'饥饿加载', 让ribbon一开始就很快
       ribbon.eager-load.enable=true                      // 开启 [饥饿加载]
       ribbon.eager-load.clients=user-center,aaa,bbb      // 指定的会 [饥饿加载], 不指定的还是 [懒加载]


3->扩展Ribbon————配合Nacos
   -) '扩展Ribbon————支持Nacos权重'
       Nacos权重: Nacos的权重可以通过'控制台'去配置, 取值越大, 表示被调用的记录也就越大
       Ribbon负载: ribbon的负载策略是'不支持Nacos'的, 所以这就需要我们'自定义负载策略'来支持Nacos
       实现: 附录/权重/NacosWeightedRule.java
       配置:
       - [局部配置]
          user-center.ribbon.NFLoadBalancerRuleClassName=com.netflix.balancer.RandomRule

       - [全局配置]
          @Configuration
          @RibbonClient(name = "eureka-client", configuration = {com.netflix.loadbalancer.RandomRule.class})
          public class RibbonLBConfig {

              //    @Bean
              //    public IRule ribbonRule() {
              //        return new NacosWeightedRule();
              //    }

          }

       思考: 竟然[nacos]已经有基于权重的负载算法了, 那么为什么还要去整合ribbon呢, 直接用[nacos-client]不就OK了吗...
       原因:
       是这样, 主要是为了符合springcloud的标准
       spring cloud 子项目 ----> [spring cloud commons] 子项目 ----> [spring cloud loadbalancer]
       而[spring cloud loadbalancer]里面指定了一系列的'负载策略', 然后是没有[权重概念],
       所以, 我们知道[spring cloud alibab]遵循了该标准, 整合了ribbon


   -) '扩展Ribbon————同集群优先调用'
       描述:
       为了保证高可用, 我们进行容灾处理, 将服务部署在'北京 - 上海 - xxx', 然后各个地区的集群下有很多实例,
       那么负载时怎么达到: 北京用户优先调用[北京集群的实例], 不会调用到[上海的实例], 当北京实例'全部不可用'时, 在调用[上海实例]

       解决方案:  此场景正好使用Nacos的['Cluster']就可以完美演绎

       核心:
       1. 基于nacos的[领域模型 - 层层嵌套]
       2. 根据服务名称, 找到该服务的所有实例 ---> ListA
       3. 过滤出'相同集群下'的所有实例      ---> ListB
       4. 如果[ListB 为空], 那么就使用 [ListA]
       5. 基于权重的负载算法, 返回一个实例

       详见: 附录/权重/NacosSameClusterWeightedRule.java
       技巧: 如果源码中出现[protected]关键子修饰的方法, 我们无法调用的话, 这个时候我们可以建一个新类, 去[extends]该类, 这样就可以使用该方法了...
       说明: 这个大目老师已经给官方提了, 以后可能就不需要我们自己实现该负载算法了, [spring cloud alibaba]后续可能会实现相关的负载算法...


   -) '扩展Ribbon————基于元数据的版本控制'
       描述:
       此场景是这样的, 如果我们新发布了一个版本功能, 但是不想让[某些服务]调用到该实例, 那么就可以使用自定义的策略来隔离开.
       https://www.imooc.com/article/288674
       详见: 附录/权重/NacosFinalRule.java


   -) 'namespace'
       nacos里面的[namespace]配置, 其实是'隔离服务调用',
       场景1: 服务A配置了[ns1], 服务B没有配置[ns], 那么服务B 不能调用 服务A
       场景2: 服务A配置了[ns1], 服务B没有配置[ns2], 那么服务B 也是不能调用 服务A
       总结: 如果配置了ns. 那么各个服务想要通信的话, 就'必须保持'相同的ns!

   遗留问题:
   . 代码不可读
   . 复杂的url难以维护 (http://user-center/s?id={id}&name={name})
   . 编程体验很不友好


4->Feign
   这里就不做具体的说明了, 直接说优化
   Feign默认使用[urlconnection]进行请求, 我们使用[httpclient | okhttp]配置'连接池', 性能大概提升[15%]左右
   使用:
   [httpclient]
   1). pom
       // 这里[springcloud]为我们指定了版本
       <dependency>
         <groupId>io.github.openfeign</groupId>
         <artifactId>feign-httpclient</artifactId>
       </dependency>

   2). 配置
       feign.httpclient.enable=true
       feign.httpclient.max-connections=200           // feign的最大连接数
       feign.httpclient.max-connections-pre-route=50  // feign单个路径(方法)的最大连接数

   [okhttp]
   1). pom
       // 这里[springcloud]没有为我们指定了版本, 所以需要手动指定
       <dependency>
         <groupId>io.github.openfeign</groupId>
         <artifactId>feign-okhttp</artifactId>
         <version>10.1.0</version>
       </dependency>

   2). 配置
       feign.okhttp.enable=true
       feign.okhttp.max-connections=200           // feign的最大连接数
       feign.okhttp.max-connections-pre-route=50  // feign单个路径(方法)的最大连接数

   常见问题: https://www.imooc.com/article/289005
   https://www.imooc.com/t/1863086



5->Sentinel
   -) '遗留问题'
       问题:
       至此, 微服务的'注册中心 - 服务通信 - 通信负载'都实现了, 但是问题来了, 如果在我们的服务比较多, 当某个服务发生问题,
       导致没办法正常相应, 并且调用方也没有做好'防范措施'的话, 那么在整个架构中, 所引发的'连带责任', 就叫做'雪崩问题'.

       注意: '雪崩效应'一般发生在'多级服务'之间, 因为如果我们的架构是'单体架构'的话, 那么就不存在雪崩一说, 只是该项目服务不可用.

       解决:
       . 超时 (这个是很容易想到的, 当服务间请求时设定一个'超时时间', 一旦达到该超时时间, 就直接拒绝)
       . 限流 (比如下游服务能接收的'QPS'为800, 那么我们设置当达到该值时, 就直接拒绝)
       . 仓壁模式 (通俗来讲就是: 把一个资源分成若干个小资源, 当某些小资源发生异常时, 不会影响到[主资源]的正常工作, 这个可以使用[线程池]来进行隔离 (thread-pool-A, thread-pool-B))
       . 断路器模式 (很常用的一种模式, 也是一般微服务容错的落地方案)
         家里都有断路器, 当某一时段电流过大时就'跳闸', 从而保护电路不被烧毁!
         而我们实际项目中使用: 可以根据某一服务[错误次数 | 错误率]达到某个值时, 进行服务拒绝, 断路器处于['半开状态'], 当过一段时间后, 断路器会'自动'尝试调用服务,
         如果调用成功, 那么断路器关闭; 如果调用失败的话, 那么断路器就'继续开启'

       总结:
       其实微服务中无论是什么样的容错手段, 其核心都是为了, 项目在发生雪崩时的'有效保护', 以及过程中的'动态恢复',
       从这一点上来说, 大家都是一样的, 只不过是'八仙过海各显神通'罢了 ^[-]^

   -) 'Sentinel'
       概念: sentinel是一个轻量级的流量控制, 熔断降级的Java库

       使用:
       1. [pom]
           <dependency>
             <groupId>org.springframework.cloud</groupId>
             <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
           </dependency>

           <dependency>
             <groupId>org.springframework.cloud</groupId>
             <artifactId>spring-boot-starter-actuator</artifactId>
           </dependency>

       查看: localhost:port/actuator/sentinel
       说明: 本身sentinel只需要引入[pom]即可, 但是这里引入[actuator]是为了查看[/actuator/sentinel]端点

   -) '控制台'
       虽然我们已经整合了sentinel(pom), 但是看起来并不直观 (一堆东西), 那么有没有可视化界面什么的呢...
       下载: https://github.com/alibaba/Sentinel/releases
       版本选择: 下载的控制台版本, 一般和[pom-sentinel]的版本一致, 防止兼容性问题!
       启动: java -jar sentinel-dashboard-1.6.2.jar
       访问: localhost:8080
       登陆: sentinel / sentinel
       配置文件: spring.cloud.sentinel.transport.dashboard=localhost:8080        // 这里替换实际生产上安装[sentinel-dashboard]的[ip:port]
       说明: 以上步骤结束后就说明整合完毕, 但是因为sentinel是'懒加载', 所以只有我们进行接口访问时, 才会在控制台看到数据呦...


   -) '流控规则'
       起因: [pom.sentinel]之后, 会将整个模块的[api]进行监控, 然后[控制台]进行操作...
       簇点链路: 里面有很多干货, 针对模块里面的所有api (流控, 降级, 热点, 授权)
       . 资源名: 唯一标识 (默认路径名: /user/findList)
       . 针对来源: 这个可以针对[服务A, 服务B...]来进行来源区分 (一般是调用方的服务名[user, product...]), 进阶版是下面的[流控模式.链路]区分
       . 阈值类型: 进行[QPS | 线程数]的阈值拦截 (默认 default, 不区分来源)
       . 流控模式:
         1. 直接 (是指: 当达到了'阈值时'的时候, 在此请求时限流)
         2. 关联 (是指: 填写的关联资源达到阈值时, 那么就限流'本路径', 下游业务会限流上游业务)
         3. 链路 (是指: 只记录指定链路上的资源, 意思是通过注解[@SentinelResource]标记的方法, 会被sentinel识别,
                 然后可以做到更细粒度的'api级别'的限流)
       . 流控效果:
         1. 快速失败 (顾名思义, 就是达到了阈值后直接抛异常 [com.alibaba.csp.sentinel.slots.block.flow.controller.DefaultController])
         2. WarmUp (预热)
            是指: 根据codeFactor(默认3)的值, 从[阈值/codeFactor], 经过预热时长, 才能达到设置的QPS阈值
            简单理解: 通过设置'预热时长', 从[阈值/codeFactor]开始计算, 当经过预热时长后, 达到[设定阈值], 常用于[秒杀场景]
            例如: 阈值=100, 预热时长=10, 那么就是: 100/3=33, 在[10s]内从33达到100
            源码: [com.alibaba.csp.sentinel.slots.block.flow.controller.WarmUpController]
         3. 排队等待 (匀速排队)
            让请求以'均匀'的速度通过, 注意: 这里的阈值类型'一定是QPS', 否则无效)
            源码: [com.alibaba.csp.sentinel.slots.block.flow.controller.RateLimiterController]

   -) '降级规则'
       源码: [com.alibaba.csp.sentinel.slots.block.degrade.DegradeRule.passCheck]
       . 资源名: 唯一标识 (默认路径名: /user/findList)
       . 降级策略
         - RT (平均响应时间, 纬度: 秒级)
           1. RT (单位:毫秒, 简单理解为[接口响应时长]) 例如: 1
           2. 时间窗口 (单位:秒, 维持断路器开启的时间) 例如: 5
           说明: 当[/user/findList]秒级统计'大于'1毫秒 并且 在5秒内通过的请求'大于等于'5次, 那么就会触发降级(断路器打开), 然后在[5秒后], 断路器关闭, 降级关闭
           注意:
           . RT的时间默认最大为[4900ms], 我们输入超出了这个限制的话, 那么也会按这个来. 强制修改: -Dcsp.sentinel.statistic.max.rt=xxx

         - 异常比例 (纬度: 秒级)
           流程:
           [QPS >= 5 && 异常比例 (秒级统计) 超过阈值] ------> [触发降级 (断路器打开)] ------> [时间窗口结束] ------> [关闭降级 (断路器关闭)]

         - 异常数 (纬度: 分钟)
           流程:
           [异常数 (分钟统计) 超过阈值] ------> [触发降级 (断路器打开)] ------> [时间窗口结束] ------> [关闭降级 (断路器关闭)]
           注意: 对于该模式来说, 是根据[分钟纬度]来统计的, 所以: 时间窗口 < 60秒可能会出问题! 建议: [时间窗口 >= 60]

         说明: 断路器状态: [开启 <-> 半开 <-> 关闭], 但是sentinel的断路器'没有半开'状态.

   -) '热点规则'
       也称[热点参数限流规则], 可以对API的[指定参数 | 指定参数的类型]进行限流, api搭配注解实现
       @SentinelResource("hostRule")
       public String hostRule(@RequestParam(required = false) String p1, @RequestParam(required = false) String p2) {
          return pa + " ---> " + p2;
       }

       说明: 这样就会暴露[hostRule]这个端点, 然后根据此端点进行限流操作, 然后可以根据[第几个参数]进行限流, 以及根据[参数类型, 参数值]进行限流!
       分析: 此模式可以在根据参数进行[秒杀场景]下进行限流, 比如: type=phone 的请求限流, 而type=eat的商品正常请求...提升了单接口的'可复用性'!
       适用场景:
       适用于针对某些'热点参数', 并希望提升接口的复用性的场景
       比如, 同样是秒杀商品, 只是类型不同的商品, 流量大有不同, 不重复写接口去满足这些'大流量类型'场景
       注意: 参数类型'必须'是[基本类型]或者[String], 否则不生效!
       源码: [com.alibaba.csp.sentinel.slots.block.flow.param.ParamFlowChecker.passCheck]

   -) '系统规则'
       . load
         说明: 负载, 当系统load1(1分钟的load)超过阈值, 且并发线程数超出'系统容量'时触发, 建议设置为 [CPU核心数 * 2.5]
         系统容量:
         系统容量 = maxQps * minRt
         - maxQps: 秒级统计出来的最大QPS
         - minRt: 秒级统计出来的最小响应时间
         源码: [com.alibaba.csp.sentinel.slots.system.SystemRuleManager.checkBbr]

         注意: 该模式仅对[Linux/Unix-like]机器生效, Win无效
              - 查看load: uptime
                load averages: 2.09 2.25 2.22
                2.09: 1分钟的系统负载 (load1)
                2.25: 5分钟的系统负载 (load5)
                2.22: 15分钟的系统负载 (load15)

       . 系统-RT: 所有入口流量的平均RT达到阈值触发 (源码: [com.alibaba.csp.sentinel.slots.system.SystemRuleManager.checkSystem])
       . 线程数: 所有入口流量的并发线程数达到阈值触发
       . 入口QPS: 所有入口流量的QPS达到阈值触发

   -) '授权规则'
       可以区分来源, 进行配置'黑白名单', 用于限制[部分微服务]是否可以请求此微服务的接口


   -) '代码配置'
       以上都是通过[控制台]进行流量控制的, sentinel也支持'代码'模式来控制
       详见: http://www.imooc.com/article/289345
       说明: 如果只想使用限流的话, 那么控制台无疑是最佳选择; 但是, 如果你想更深入理解的话, 那么代码模式还是很好的


6->Sentinel与控制台不得不说的那些事
   写在前面: 以下我们所做的这些心血, 都是为了'保护我们的架构', 提高'可用性'!!!

   -) '思考'
       竟然控制台那么好用, 我们上手也很快, 那么...
      . 控制台是如何获取到微服务的监控信息的
      . 用控制台配置规则时, 控制台是如何将规则发送到各个微服务呢

      通信:
      因为各个模块都引入了[sentilen], 而sentinel充当了[注册中心]的角色, 然后各个服务又配置了[dashboard]的地址,
      所以各个模块就可以通过[端口号]与控制台进行交互 ^-6

      源码:
      . 注册/心跳检测: [com.alibaba.csp.sentinel.transport.heartbeat.SimpleHttpHeartbeatSender]
      . 通信API: [com.alibaba.csp.sentinel.command.CommandHandler]的实现类

   -) '控制台配置项'
       [配置项]                                           [默认值]      [最小值]   [描述]
       sentinel.dashboard.app.hideAppNoMachineMillis        0          60000    是否隐藏无健康节点的应用, 距离最近一次发生心跳时间的毫秒数, '默认关闭'
       sentinel.dashboard.removeAppNoMachineMillis          0          120000   是否自动删除无健康节点的应用, 距离最近一次其下节点的心跳时间毫秒数, '默认关闭'
       sentinel.dashboard.unhealthyMachineMillis            60000      30000    主机失联判定, 不可关闭
       sentinel.dashboard.autoRemoveMachineMillis           0          300000   距离最近心跳时间超过指定时间, 是否自动删除失联节点, '默认关闭'
       server.port                                          8080       -        指定端口
       csp.sentinel.dashboard.server                        localhost:8080  -   指定地址
       project.name                                         sentinel   -        指定程序的名称
       sentinel.dashboard.auth.username [1.6]               sentinel   -        Dashboard登陆账号
       sentinel.dashboard.auth.password [1.6]               sentinel   -        Dashboard登陆密码
       server.servlet.session.timeout [1.6]                 30分钟      -       登陆Session过期时间 / 配置为7200表示7200秒 / 配置为60m表示60分钟

       使用: java -jar -Dsentinel.dashboard.auth.username=xm -Dsentinel.dashboard.auth.password=123456 sentinel-dashboard-1.6.2.jar

   -) '自定义资源保护'
       说明: 针对没有开放[sentinel控制台], 需要我们想自定义资源保护的话, 那么这就派上用场了!
       核心:
       . SphU: 定义资源, 让资源收到监控并保护
       . Tracer: 统计自定义异常类, 认只监控[BlockException]的次数,
       . ContextUtils: 定义资源且能定义针对来源, 设置黑白名单

   -) '@SentinelResource —— 小帮手'
       详见: http://www.imooc.com/article/289384
       说明: 我们上面整的好麻烦, 那么有没有更简洁的方式来配置我们的规则呢...
       代码: 附录/resource/
       感悟: 降级的表现形式和[Hystrix]大同小异, 都是定义异常后执行的方法, 而且也都支持'外部化-方法处理类', 并且方法参数和返回值都和'原方法一致'
       源码:
       . [com.alibaba.csp.sentinel.annotation.aspectj.SentinelResourceAspect]
       . [com.alibaba.csp.sentinel.annotation.aspectj.AbstractSentinelAspectSupport]

   -) '整合 - RestTemplate'
       说明: 整理起来是很简单的, 只需要一个注解即可 ^-^
       @Bean
       @LoadBalanced
       @SentinelRestTemplate
       public RestTemplate restTemplate() {
           return new RestTemplate();
       }
       说明: 这个开启后的降级界面是很不友好的, 该注解里面可以配置[block | fallback]

       配置文件: resttemplate.sentinel.enbale=false         // 关闭sentilen注解, 适用于开发过程中, [不想]被sentinel降级所干扰

       源码: [org.springframework.cloud.alibaba.sentinel.custom.SentinelBeanPostProcessor]

   -) '整合 - Feign'
       说明: 整合Feign更是简单的没得说 ^-^
       整合: feign.sentinel.enabled=true        // idea不会给我们提示, 但是又确实是[生效的], 有点[彩蛋]的意思哈...
       问题1: 因为只需要在配置文件中开启就行了, 虽然sentinel可以正常监控, 但是报错的页面提示很不友好, 而且不像[RestTemplate]可以在注解里配置, 那么Feign要怎么处理呢...
       解决1: 其实Feign的流控解决实现和[Hystrix]一样, 都是在[@FeignClient(fallback = "")]来指定异常的处理逻辑

       问题2: 虽然可以处理异常情况, 但是怎么获得异常信息呢, 因为我不知道异常信息的话, 何从处理呢...
       解决2: 在[解决1]的基础上, 使用 fallbackFactory = ""

       详见: 附录/feign/UserCenterFeignClientFactory.java
       源码: [org.springframework.cloud.alibaba.sentinel.SentinelFeign]


7->规则持久化
   说明: 如果我们'千辛万苦'配置好的[sentienl规则], 项目'起停'后, 结果配置无了, 这🉑️还得了!!!...>-<
   -) '规则持久化'
       拉模式: http://www.imooc.com/article/289402
       推模式: http://www.imooc.com/article/289464

   -) '生产使用'
       说明: 如果我们想使用sentinel, 但是还不想实现[推拉模式], 就只是想拿来就用! 还有这好事吗...
       福利: 还别说, 真有这好事!
       1. [开通]
          AHAS:
          开通地址: https://ahas.console.aliyun.com/
          开通说明: https://help.aliyun.com/document_detail/90323.html
          然后都处理好后, 进入[主页面 -> 介入应用流控]

       2. [pom]
          <dependency>
            <groupId>com.alibaba.csp</groupId>
            <artifactId>spring-boot-starter-ahas-sentinel-client</artifactId>
            <version>1.3.3</version>
          </dependency>

          <dependency>
              <groupId>org.springframework.cloud</groupId>
              <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
              <exclusions>
                  <exclusion>
                      <groupId>com.alibaba.csp</groupId>
                      <artifactId>sentinel-transport-simple-http</artifactId>
                  </exclusion>
              </exclusions>
           </dependency>
           说明: 因为我们使用了'托管配置中心', 所以就不需要'sentinel-默认与nacos通信'的依赖了

       3. [http埋点 | 普通接口埋点]
          可以省略, 因为[spring-cloud-alibaba]已经为我们做了

       4. 配置启动参数
          project.name=appName
          ahas.namespace=default
          ahas.license=uhu2jnsfh65675a8faa57a78


8->集群流控
   单体服务就图一乐, 高可用还得看'集群' ^0^
   环境: 纯净[SpringCloudAlibaba], 无[持久化规则], 无[ahas], 服务只需要[连接控制台]
   架构:
                      +--------------+
                      | Token Server |
                      +-------+------+
                              |
           +------------------+-----------------+
           |                  |                 |
   +--------------+   +--------------+  +--------------+
   | Token Client |   | Token Client |  | Token Client |
   +--------------+   +--------------+  +--------------+
   说明:
   竟然我们想要使用[集群模式], 那么设想一下, 这个集群肯定需要一个[领导者]负责'各个服务'的监控, 那么就要拿到各个微服务流控规则, 这样才能'识别出'各个服务的状态 (QPS)
   每个服务集成[Token Client], 然后向[Token Server]汇报自身QPS, 然后[Token Server]根据QPS以及监控规则进行'统一管理', 这样, 就实现了'集群'!

   注意: 但是目前sentinel还'未实现'集群, (有些问题还未解决: 高可用, 选举...)



9->扩展Sentinel
   -) '错误页优化'
       问题描述: 通过控制台, 我们配置了流控规则 (降级, 限流), 但是在实际发生时, 我们的页面却显示'默认报错提示', 这很不友好, 而且'无法区分'异常来源(降级 | 限流)
       解决: 其实sentinel也考虑到这点了, 但是之所以没有真正实现, 是因为sentinel并不知道异常提示内容, 所以把'想象空间'给我们自己定义! (亚撒西得丝乃 ^.^)
       核心: UrlBlockHandler (接口)
       其中 [BlockException]异常类, 里面有[ 5 ]个异常实现, 分别对应:
       FlowException         => 限流异常
       DegradeException      => 降级异常
       ParamFlowException    => 热点参数流控规则异常
       SystemBlockException  => 系统规则异常
       AuthorityException    => 授权规则异常

       详见: 附录/sentinel_expand/MySentinelException.java

   -) '区分来源'
       问题描述: [流控规则 -> 区分来源 / 授权规则 -> 流控应用] 都需要'区分来源'才可以使用, 那么究竟什么是'区分来源呢', 又'为什么区分来源'...
       解决:
       我们知道, 接口调用就需要携带一些接口的信息, 如果我们的接口不想要被外界调用, 那么就可以指定'哪些外界'不能调用, 或者'可以调用',
       这个'外界'就是来源, 我们对这个外界所做的操作 (黑白名单), 就是'针对来源'
       核心: RequestOriginParser (接口)
       详见: 附录/sentinel_expand/MyRequestOriginParser.java

   -) 'RESTful URL支持'
       问题描述:
       如果我们的接口需要配置流控规则, 而且接口形式是这样: [/user/1]那么正常配置是没有问题的, 但是后面又有[/user/2],
       而后面这个[/user/2]的接口'竟然没有'和[/user/1]实现'相同的规则'! 而sentinel又没有针对'占位符'的配置!
       我们总不能为每个[/user/xxx]都配置相应的规则吧! 所以, 我们急需解决这个问题!!!
       解决:
       虽然没有占位符的配置, 但是我们发现其实请求都是'同一来源 => /user', sentinel也想到了这点, 并为我们预留了'后门',
       竟然不支持占位符, 那么我们就'配置成占位符'
       核心: UrlCleaner (接口)
       详见: 附录/sentinel_expand/MyUrlCleaner.java

       总结:
       . UrlBlockHandler: 提供Sentinel异常处理
       . RequestOriginParser: 来源支持
       . UrlCleaner: 重新定义资源名
       详见: https://www.imooc.com/article/289562

   -) '常见容错方案'
       Sentinel: 易学难精
       Hystrix: 线程池隔离 / 信号量隔离 (仓壁模式 - 小型线程数太多, 反而影响性能)
